
// Copyright (C) 2007 Richard Uhler
// This file is part of Esiply.
//
// Esiply is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, Version 2,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.


#include <assert.h>

#include "Completion.h"
#include "NormalFunction.h"
#include "List.h"
#include "Null.h"
#include "Number.h"
#include "Object.h"
#include "Scope.h"
#include "String.h"
#include "Undefined.h"
#include "ValueType.h"

NormalFunction::NormalFunction(Scope* scope, const char* params)
    : m_scope(scope)
{
    // Parse the params. basically, split string by ","
    // if the string is empty, no params.
    const char* p = params;
    int index = 0;
    while (p[index] != '\0')
    {
        if (p[index] == ',')
        {
            m_params.push_back(new String(p, index));
            p = p + index + 1;
            index = 0;
        }
        index++;
    }

    if (index > 0)
    {
        m_params.push_back(new String(p));
    }

    this->put(new String("length"), new Number((double)m_params.size()),
            PA_DONT_DELETE | PA_READ_ONLY | PA_DONT_ENUM);
}

Value* NormalFunction::call(Scope* scope, Value* this_obj, List* args)
{
    // Ecma 13.2.1
    // 1. Establish a new execution context as described in 10.2.3
    // Create the variable object (the "activation" object) for the call
    Object* obj = new Object();
    Scope* nscope = new Scope(m_scope, obj, this_obj);

    // 10.1.3
    assert(args->getType() == VT_LIST);
    unsigned int i;
    for (i = 0; i < m_params.size() && i < args->size(); i++)
    {
        // TODO: attributes determined by the type of code
        obj->put(m_params[i], args->get(i), PA_EMPTY);
    }

    // If the caller supplies fewer parameter values than there are formal
    // parameters, the extra formal parameters have value undefined
    for ( ; i < m_params.size(); i++)
    {
        obj->put(m_params[i], Undefined::undefined, PA_EMPTY);
    }

    // Ecma 10.1.6 Activation object (obj)
    // obj is initialised with a property with the name "arguments" and
    // attributes {DontDelete}. The value is...
    // Ecma 10.1.8
    Object* arguments = new Object();
    obj->put(new String("arguments"), arguments, PA_DONT_DELETE);

    // A property is created with name "callee" and property attributes
    // {DontEnum}. The initial value of this property is the Function
    // object being executed
    arguments->put(new String("callee"), this, PA_DONT_ENUM);

    // A property is created with name "length" and property attributes
    // {DontEnum}. The initial Value of this property is the number of 
    // actual parameter values supplied by the caller.
    arguments->put(new String("length"), new Number(args->size()),
            PA_DONT_ENUM);

    // Add all the arguments
    for (unsigned int i = 0; i < args->size(); i++)
    {
        String* index = (new Number(i))->toString();
        arguments->put(index, args->get(i), PA_DONT_ENUM);
    }

    // 2. Evaluate the function body
    Value* res = this->evaluate(nscope);

    // 3. Exit the execution restoring the previous context (nothing to do)
    // 4. If res.type is throw, then throw res.value
    assert(res->getType() == VT_COMPLETION);
    if (res->completion()->type() == CT_THROW)
    {
        return res;
    }

    // 5. If res.type is return, then return res.value
    if (res->completion()->type() == CT_RETURN)
    {
        return res->completion()->value();
    }

    // 6. res.type must be normal, return undefined
    assert(res->completion()->type() == CT_NORMAL);
    return Undefined::undefined;
}

Value* NormalFunction::construct(List* args)
{
    // Ecma 13.2.2 [[Construct]]
    // 1. Create a new native ECMAScript object
    //  (I'll do that after I get the prototype for it)
    // 2. set the [[Class]] property of that to "Object"
    // 3. Get the value of the prototype property of F.
    // 4. if proto is an object, set the [[Prototype]] property of the
    // new object to proto
    Value* proto = this->get(new String("prototype"));

    // 5. if that's not an object, set the prototype of the new object
    // to the original prototype object
    if (proto->getType() != VT_OBJECT)
    {
        proto = Object::prototype;
    }

    // (this is our execution of step 1.)
    Object* obj = new Object(Null::null, proto->object(), new String("Object"));

    // 6. Invoke the [[call]] property of F, providing obj as the this value
    // and providing the argument list passed into us
    Value* res = this->call(NULL, obj, args);

    // 7. If type of res is Object, return it
    if (res->getType() == VT_OBJECT)
    {
        return res;
    }

    // 8. return obj
    return obj;
}

